/*此处实现的http服务器框架，对标于nginx，httpd*/
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/sendfile.h>
#include <sys/wait.h>
#include "http_server.h"


typedef struct   sockaddr sockaddr;
typedef struct   sockaddr_in sockaddr_in ;



//一次从socket中读取一行数据，把数据放在buf缓冲区中；如果读取失败，返回值就是-1；
//遇到\n或\r\n或\r证明读取完成（一般为'\n',但是有些浏览器换行符是'\r','\r\n')
int ReadLine(int  sock,char  buf[],ssize_t  size){
    //size表示缓冲区的长度
	//1.从socket中一次读取一个字符
	char  c='\0';
	ssize_t i=0;//i表示当前读了多少个字符
	//结束条件；
	//a)读到的长度太长，达到了缓冲区长度的上限
	//b)读到了结束标志，并把所有结束标志都转换成\n（此处要兼容\r,\r\n的情况，统一把这两种情况
    //转换为\n

	while(i<size-1&&c!='\n'){
		ssize_t  read_size=recv(sock,&c,1,0);
        //一次读一个字符，把字符读到c总，0表示flag为0
		//读取失败有两种
		if(read_size<0){
			return -1;
		}
		if(read_size==0){
			//因为预期是要读到\n这样的换行符，
            //结果还没有读得到就先读了EOF，这种情况我们也暂时认为是失败的
			return -1;
		}
        //此时表示大于0的情况
		if(c=='\r')
		{
			//当前遇到了\r,但还需要确定下一个字符是不是\n，尝试读取下一个字符
			//MSG_PEEK选项从内核的缓冲区中读取数据，
            //内核中的缓冲区直接进行recv(sock,&c,1,0),
            //一次读一个字符，并且把字符读到c中，同一时刻删除缓冲区中的字符，这就意味着，再次调用
            //recv读到就是第二个字符，紧接着第二个字符也从缓冲区中删除。
            //但是如果使用recv(sock,&c,1,MSG_PEEK);还是将字符读取出来，但是缓冲区中的字符仍然还在
            //下次再次读取，读到的还是上一次读取的字符。因为不知道下一个要读取的字符是哪个，先看
            //要读取的下一个字符是什么，再决定要不要把字符从缓冲区中取出来。
			//但是读到的数据不会从缓冲区中删除掉。
			recv(sock,&c,1,MSG_PEEK);
			if (c=='\n'){//如果下一个字符是\n
				//此时的分隔符就是\r\n,那么就不要这个字符了，下次再读取的就是下一行的开始
				recv(sock,&c,1,0);
			}else 
			{
				//当前分隔符确定是\r，此时把分隔符转换成\n
				c='\n';
			}
		}
		//只要上面的c读到的是\r,那么if结束后，c都变成了\n
		//这种方式就是把前面的\r和\r\n两种情况都统一成了\n
		buf[i++]=c;

	}
	buf [i]='\0';
	return i;//真正想缓冲区中放置的字符的个数
}


int Split(char  input[], const char *split_char,char* output[],int output_size)
{
    //split_char:分隔符；
	//使用strtok_r
	int i=0;
	char *tmp=NULL;//保存上次的切分结果
	char * pch;
	//使用线程安全的strtok_r代替strtok
	//这一点是以后非常容易出错的一点
	pch = strtok_r (input,split_char,&tmp);
	while (pch != NULL)
	{
		if (i>=output_size)
		{
			return i;
		}
		output[i++]=pch;
		pch = strtok_r (NULL, split_char,&tmp);//继续前面的切分继续切分
	}

	//printf ("Split\n");
	return i;//返回切分的个数

}
//strtok是一个字符串切分函数，一次只能切分一个部分，但是实际中大多期望一次切分完所有的，所以采用
//循环调用的方式。
//strtok的缺陷：可能会导致服务器崩溃，strtok可以接着上一次的切分结果进行切分，说明strtok中保存
//了上一次的切分结果，strtok不能用栈上的变量保存，因为函数一旦结束，函数栈上的空间就没有了；
//也不能用堆上的数据保存，因为不确定变量什么时候进行释放。调用一次释放一次，又会导致内存泄漏
//静态变量的生命周期是全局的，使用静态变量来保存，函数结束，静态变量仍然存在；再次调用strtok，变量
//的值，仍然为上一次的结果。所以strtok内部采用静态变量来保存上一次切分的位置。函数内部使用静态变量
//意味着不可重入，说明此函数也是一个线程不安全函数（多个线程同时调用，可能会出现逻辑上的问题。）
//Split函数是一个多线程的函数，每次收到一个请求，就创建一个线程，线程都要进行first_line解析，
//每次解析都得调用strtok_r进行切分，在一个多线程的环境下调用一个线程不安全函数，很可能导致程序崩溃
//不应该使用线程不安全函数在一个多线程环境下进行调用。
//解决这个问题的办法：a、加互斥锁：互斥锁可行，但是没必要。枷锁意味着函数在不同的线程之间要相互竞争
//锁，会导致程序整体的效率被拖慢，很大一部分时间都在等待锁
//b、使用strtok_r函数，是strtok的线程安全版本，不再使用内部的缓冲区，而是通过函数参数的形式显示的
//指定一个缓冲区。这个缓冲区提供一个空间，帮助函数保存上一次的执行结果。
//类似使用内部静态缓冲区的线程不安全函数：a、inet_ntoa把数字的IP地址转换为点分十进制的ip地址，通过
//静态缓冲区保存点分十进制的字符串，同样也是线程不安全；为了解决这个问题通常使用inet_ntop函数解决
//线程不安全问题，不在使用静态缓冲区来保存结果，而是通过栈上开辟内存，把栈上缓冲区中的内容传到
//函数里面，然后基于栈上缓冲区访问结果而解决问题
//b、random函数，也是在内部有一个静态缓冲区，也是线程不安全函数，为了解决这个问题，使用random_r在
//多线程环境下使用

//解析首行
int ParseFirstLine(char  first_line[],char**p_url,char  **p_method)
{
    //需要找到哪部分是url，哪部分是method，此时我们考虑
    //一个指针指向方法的首地址，一个指针指向url的首地址的情况
    //为了得到每一个部分，把空格替换成\0，再通过指针指向每一个部分的起始位置，就得到了每一个部分
    //对应的字符串，破坏了原有的字符串结构，将其切分成了若干个部分，若干个部分之间不再用空格
    //切分，直接用\0切分，我们再得到每一个部分起始位置的指针，就可以通过c风格字符串的方式，得到
    //方法、url……
    //char **：参数url和method是通过函数参数传递，是一个输出型参数，输出两个指针的指向位置，只能
    //用二级指针的方式修改指针的指向。所以需要将指针的地址传递给函数。
	//把首行按空格进行字符串切分
	//切分得到的每一个部分，就放在tok数组里
	//返回值，就是tok数组包含几个元素。

	char  *tok[10];
	//最后一个参数10表示tok数组中最多可以放几个元素
	int tok_size=Split(first_line," ",tok,10);
	if (tok_size!=3)
	{
		printf ("Split  failed! tok_size =%d\n",tok_size);
		return -1;
	}
	*p_method=tok[0];
	*p_url=tok[1];

	// printf ("ParseFirstLine\n");
	return 0;
}


//把url字符串进一步切分：url_path指向问号之前、空格之后的字符串；query_string指向问号之后的字符串
//把问号替换程\0，
int  ParseQueryString (char  *url,char  **p_url_path,char **p_query_string )
{
	*p_url_path=url;//从url起始位置开始遍历
	char *p=url;
	for (;*p!='\0';++p)
	{
		if (*p=='?')
		{
			*p='\0';
			*p_query_string =p+1;
			return 0;
		}
	}
	//循环结束都没有找到?,说明这个请求不带query_string
	*p_query_string=NULL;
	return 0;
}


//解析header
int    ParseHeader (int sock ,int *content_length)
{
	//1.循环从socket中读取一行。
	//2.判断当前行是不是Content—Length
	//3.如果是Content-Length就直接把value读出来
	//4.如果不是就直接丢弃
	//5.读到空行，循环结束。
	char buf [SIZE]={0};//定义一个缓冲区
	while (1)
	{
		//1.循环从socket中读取一行。
		ssize_t  read_size=ReadLine (sock,buf,sizeof (buf));
        //sizeof（buf）没有减1，是什么原因？ReadLine函数中已经考虑了\0的情况，此时可以不考虑\0
        //的情况，但是双重保证就更合适。
		//处理读失败的情况
		if (read_size<=0)
		{
			return -1;
		}
		//处理读完的情况
        //buf里面只有一个\n，说明读完了
		if (strcmp (buf ,"\n")==0)
		{
			return 0;
		}
		//2.判定当前行是不是Content-Length
		//如果是Content-Length就直接把value读出来
		//如果不是就直接丢弃
		const  char *content_length_str="Content-Length: ";
		if (content_length!=NULL&&strncmp(buf,content_length_str,strlen(content_length_str))==0)
		{
			*content_length=atoi(buf+strlen(content_length_str));
//strncmp比较前n个字符；注意“Contnt-Length”的写法，只有这种格式。
		}
	}
	return 0;
//更复杂的版本需要把这个字符串以键值对的方式存储（key和value）
}
//刚刚保存时出现了'readonly' option is set (add ! to override)
//解决方案:按下ESC,输入:set noreadonly；然就就可以使用:wq正常保存了


void Handler404(int sock)
{
	//构造一个完整的HTTP响应
	//状态码是404
	//body部分也应该是一个404相关的错误页面。
	const char*  first_line="HTTP/1.1 404 Not Found\n";
	const char*  type_line="Content-Type: text/html;charset=utf-8\n";//提示浏览器按照utf-8方式解码于下面成双重保证
	const char *  blank_line="\n";
//html是一个文本文件，用于说明标题，……详细的信息
	const char * html="<head><meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\"></head>"
		"<h1>您的页面没找到！！！</h1>";//提示浏览器按照utf8方式解码//否则会产生乱码//
    //可以通过:set fileencode查看编码格式，charset叫做字符集
	//构造了一个http响应并且把此响应写会到socket之中，返回浏览器。
    send (sock,first_line ,strlen(first_line),0);
    //send有一个额外的选项flag
	send (sock,type_line ,strlen(type_line),0);
	send (sock,blank_line ,strlen(blank_line),0);
	send (sock,html ,strlen(html),0);
	return;
}



void PrintRequest (Request*req)
{
	printf ("method: %s\n",req->method);
	printf ("url_path: %s\n",req->url_path);
	printf ("query_string: %s\n",req->query_string);
	printf ("content_length: %d\n",req->content_length);
	return;
}



int IsDir(const char *file_path)
{
	struct stat st;
	int ret=stat(file_path,&st);
	if (ret<0)//可能是文件不存在，则不是目录
	{
		return 0;
	}
	if (S_ISDIR(st.st_mode))
	{
        //通过这个宏，判定st_mode选项是不是目录，
		return 1;
	}
	return 0;
}

//http服务器的根目录:用户指定的任意一个存在的目录作为根目录，url_path中访问的文件都是以http服务器的
//根目录为基准，取相对路径。假如：在http_server的路径下有一个wwroot的目录，这个目录下包含了一个
//index.html的文件，如何访问这个index.html，可以通过/index.html访问，是在wwwroot下取相对路径index.html
//若是该目录下有一个文件image/1.jpg，想要取到1.jpg访问时path变为/image/1.jpg
void HandlerFilePath(const  char*url_path,char  file_path[])
{
    //把url_path转换成本地真实的文件路径，即进行字符拼接。
	//(a,给url_path 加上前缀名（HTTP服务器的根目录）
	//url_path-->/index.html
	//file_path--->./wwwroot/index.html
	sprintf (file_path,"./wwwroot%s",url_path);
    //向file_path的输出缓冲区中进行拼接
	//b)例如url_path是/，此时url_path 其实就是一个目录。
	//如果是目录的话，就给这个目录之中追加一个文件（默认文件）index.html 
	//url_path, /或者 /image/
	if (file_path[strlen(file_path)-1]=='/')
        //判定最后一个字符是不是'/'
	{
		strcat(file_path,"index.html");
        //字符串追加，因为以'/'结尾，追加时，不需要加'/'
	}
	//c)url_path=>/image，
	if (IsDir(file_path))
	{
		strcat (file_path,"/index.html");
	}
	return ;

}



ssize_t  GetFileSize(const char*  file_path)
{
	struct stat  st;
    //stat函数可以获取一些文件的源信息
	int ret=stat(file_path,&st);
    //把文件对应的原信息放到结构体里
	if (ret<0)
	{
		//打开文件失败，很可能是文件不存在
		//此时直接返回文件长度为0
		return  0;
	}
	return st.st_size;
   // st_size是文件的大小
}




int WriteStaticFile (int sock,const char*  file_path)
{
	//1.打开文件
	int fd=open (file_path,O_RDONLY);
	//打开文件失败的情况：a、文件描述符不够用
    //b、文件不存在（用户输入的可能不正确）
    if (fd<0)
	{
        perror ("open");
		return 404;
	}
	//2.把构造出来的HTTP响应写到socket之中
	//1）.写入首行
	//2）.写入head
	//4）.写入body(文件内容)
	const char *  first_line="HTTP/1.1 200 OK\n";
	send (sock,first_line,strlen(first_line ),0);
    //插入图片
    //图片和文本应该分开进行解析，或者让浏览器自动识别
	//const char*  type_line="Content-Type: text/html;charset=utf-8\n";//用此方式，解析照片时会发生错误
	//const char*  type_line="Content-Type: imag/png;charset=utf-8\n";
	//send (sock,type_line ,strlen(type_line),0);
	//两个都不添，让浏览器自己决定
	//(3)写入空行
    const char *  blank_line="\n";
	send (sock,blank_line ,strlen(blank_line),0);
	/*ssize_t  file_size=GetFileSize(file_path);
	 //获取文件的真实长度
      ssize_t  i=0;
	  for(;i<file_size;i++)
	  {
	  char c;
      //读一个字节
	  read(fd,&c,1);
      //用户中调用read就是把文件中的每一个字符都拷贝一份到用户的缓冲区里面
      //写一个字节
	  send (sock,&c,1,0);
      //把刚刚读取到的数据写回网卡
      //这种方式会在用户空间和内核空间中拷贝多次
	  }
	  */
	sendfile(sock,fd,NULL,GetFileSize(file_path));
    //sendfile函数，第一个参数时输出的参数；第一个参数是输入的参数，第三个参数文件的参数；第四个参
    //数是拷贝的字节数，文件本身的大小
    //把文件的数据直接在内核中拷回到网卡，只在内核中进行
	//3.关闭文件
	close (fd);
	return 200;
}


//处理静态文件
int HandlerStaticFile (int sock ,Request *req)
{
	//1根据url_path获取到文件在服务器上的真实路径。
	char  file_path[SIZE]={0};
	HandlerFilePath(req->url_path,file_path);//file_path输出参数
	//2。读取文件，把文件的内容直接写道socket之中
	int err_code=WriteStaticFile (sock,file_path);
	return err_code;
}

int HandlerCGIFather(int new_sock,int father_read,int father_write,int  child_pid,Request  *req){
	//1如果是POST请求，就把body写到管道中
	if (strcasecmp(req->method,"POST")==0){
		int i=0;
		char  c='\0';
		for (;i<req->content_length;++i){
		//每一次循环都读取一个字符，读一个字符向管道中写一个字符。
            read(new_sock,&c,1);
            //前面已经对CGI读取了很多次，在头部被解析之后才调用，
            //缓冲区中首行，haader，空行都已经读取结束，存放在缓冲区中了
            //所以此时读到的数据是body
			write(father_write,&c,1);
		}

	}
	//2构造HTTP响应
	const char *  first_line="HTTP/1.1 200 OK\n";
	send (new_sock,first_line,strlen(first_line ),0);
	const char*  type_line="Content-Type: text/html;charset=utf-8\n";
	//const char*  type_line="Content-Type: image/png;charset=utf-8\n";
	send (new_sock,type_line ,strlen(type_line),0);
	//两个都不添，让浏览器自己决定
	const char *  blank_line="\n";
	send (new_sock,blank_line ,strlen(blank_line),0);
	//循环的从管道中读取数据并写入数据到socket
	char  c='\0';
    //管道如果所有的写端全部关闭再尝试继续读，read就会返回0
    //管道读端全部关闭，再尝试继续写，SIGPIPE信号导致进程异常终止
	while (read(father_read,&c,1)>0){//从father_read中读取数据，读取的数据放到c里面，
		send(new_sock,&c,1,0);
	}
	//4回收子进程的资源
	waitpid(child_pid,NULL,0);
    //wait()//handlerrequet函数是在新线程中执行的，不存在阻塞不能执行多线程的情况。
    //一个服务器同一时间收到的请求是非常多的，每一个请求都得创建单独的线程，每个线程都有
    //可能处理CGI的逻辑，就有可能创建子进程
    //此处若是调用wait，线程都调用一个wait，wait是无差别的等待，
    //可能导致线程1创建的线程被线程一回收
	return 200;
}


int   HandlerCGIChild(int child_read,int child_write,Request*  req){
	//1.设置必要的环境变量
	char   method_env[SIZE]={0};
	sprintf (method_env,"REQUEST_METHOD=%s",req->method);
	putenv(method_env);//设置环境变量
	//还需要设置QUERY_STRING(get方式时需要设置)或者是CONTENT_LENGTH（post方法需要设置）
	if(strcasecmp(req->method,"GET")==0){
		char  query_string_env[SIZE]={0};
		sprintf (query_string_env,"QUERY_STRING=%s",req->query_string);
        putenv(query_string_env);
	}else {
		char content_length_env[SIZE]={0};
		sprintf (content_length_env,"CONTENT_LENGTH=%d",req->content_length);
        putenv(content_length_env);//设置环境变量
	}//名字必须满足CGI标准
	//2把标准输入输出重定向到管道里
	dup2(child_read,0);//把第二个参数重定向到第一个参数
	dup2(child_write,1);
	//3对子进程进行程序替换
	//  url_path: /cgi-bin/test
	//  file_path: ./wwwroot/cgi-bin/test
	//根据url_path获取对应的file_path
    char file_path[SIZE]={0};
	HandlerFilePath(req->url_path,file_path);//把req->url_path生成file_path
        
	execl(file_path,file_path,NULL);//
        exit(1);//当程序替换失败时。一定让子进程终止，如果子进程不终止，因为父子进程是同一块代码,父进程一直在Listen状态一直等待,子进程也会等待,所以要直接结束进程，避免一直等待端口数据的返回；
//有  exec   l    lp  le   v   vp   ve,首先看知不知道可执行文件的完整路径 ，如有就可以不带P，因为P是在PATH中找，再看要不要环境变量，如果不用，就不需要带e,这里因为通过putenv ()的方式直接设到了ENV里面所以不用，说一直接用execl()
	return 200;
}



int HandlerCGI(int  new_sock,Request  *req)//第二个参数是格式化的请求数据
{
	int err_code=200;
	//1.创建一对匿名管道
	int fd1[2],fd2[2];
	int ret =pipe(fd1);
    //创建第一对匿名管道
	if (ret<0)
	{
		return 404;
	} 
	ret=pipe (fd2);
    //创建第二对匿名管道
	if (ret<0)
	{
        //走到这一步，说明上面的文件描述符已经分配好了，所以需要关闭前面创建好的文件描述符
		close(fd1[0]);
		close(fd1[1]);
		return 404;
	}
	//fd1,fd2这种变量名的描述性太差，后面直接用的话
	//是非常容易弄混的，所以直接在此处定义几个
	//更加明确的变量名来描述该文件描述符的用途
	int   father_read=fd1[0];
	int   child_write=fd1[1];
	int   father_write=fd2[1];
	int   child_read=fd2[0];
	//2.创建子进程
	ret=fork();
	//3.父子进程各执行不同的逻辑
	if (ret>0){

		//father
		//此处父进程优先关闭这两个管道的文件描述符
		//是为了后续父进程从子进程这里读数据时，能够读到EOF，对于管道来说，所有写端关闭，
        //继续读，才有EOF，而此时所有写端，一方面是父进程需要关闭，另一方面子进程也需要关闭。
        //所以此处父进程先关闭不必要的写端之后，后续子进程用完了
		//直接关闭，父进程也就读到了EOF
		close (child_read);
		close(child_write);
        //如果写端不关闭，随时都有可能有人往管道中写，此时应保证管道的写端全都关闭（父进程先
        //关闭，子进程用完了也关闭，两者都关闭了，才意味着全部写端端关闭，全部写端关闭，父进程一直在
        //再去读，才能读到read返回的结果
        //因为父进程先创建管道，再fork，父子进程管道各占一半
		err_code=HandlerCGIFather(new_sock,father_read,father_write,ret,req);
	}else if (ret==0){

		//child
		close (father_read);
		close (father_write);
		err_code=HandlerCGIChild(child_read,child_write,req);
	}else{
        //fd[]未初始化时，是一个随机值，直接关闭会出错
		perror("fork");
        err_code=404;
		goto END;
	}
END:
    perror("fork");
         close (fd1[0]);
         close (fd1[1]); 
        close (fd2[0]);
        close (fd2[1]);
    
	//4.收尾工作和错误处理
	return 200;
}



void  HandlerRequest(int new_sock)
{
	int err_code=200;//错误码初始200，默认没错
	//1.读取并解析请求（反序列化）
	Request req;
	memset (&req,0,sizeof (req));
	//a)从socket中读取出首行
	if (ReadLine(new_sock,req.first_line,sizeof (req.first_line))<0)
	{
		//失败处理
		err_code =404;
		goto END;
	}
	//b)解析首行，从首行中解析出url和method
	if (ParseFirstLine(req.first_line,&req.url,&req.method))
	{
		//失败处理
		err_code =404;
		goto END;

	}
	//c)解析url，从url中解析出url_path和query_string
	if (ParseQueryString(req.url,&req.url_path,&req.query_string))
	{
		//失败处理
		err_code =404;
		goto END;

	}
	//d)解析Header，丢弃了大部分header,只读取Content—Length
	if (ParseHeader(new_sock,&req.content_length))
	{
		//失败处理
		err_code =404;
		goto END;

	}
	PrintRequest (&req);
	//2.静态/动态方式生成页面
	//3.把生成结果写回客户端上
	if (strcasecmp(req.method,"GET")==0&&req.query_string==NULL)
	{
		//a)如果是GET方式请求，并且没有query_string,
		//那么返回静态页面
		err_code=HandlerStaticFile(new_sock,&req);
	}else  if (strcasecmp(req.method,"GET")==0&&req.query_string!=NULL)
	{
		//b)如果是GET方式请求，并且有query_string,
		//那么返回动态页面
		err_code=HandlerCGI(new_sock,&req);
	}else  if (strcasecmp(req.method,"POST")==0)
	{
		//c)如果请求是POST类型的（一定是带参的，参数是通过body来传给服务器的），那么也返回动态页面
		err_code=HandlerCGI(new_sock,&req);
	}else  
	{
		//失败处理
		err_code =404;
		goto END;
	}

	//错误处理；直接返回一个404的HTTP响应
END:
	if (err_code !=200)
	{
		Handler404(new_sock);
	}
	close(new_sock);
	return;

}




void *ThreadEntry(void*arg)//	因为在64位机中指针占8个字节，如果强转回int会有丢失数据的危险
//void*是一个指针，32位机器下占4个字节，64位机器下占8个字节，而一个int在32位和64位机器下都为4个字节
//在64位机器下，需要改为int64_t
{
	int new_sock=(int)arg;
	//使用HandlerRequest完成具体的处理请求过程这个过程单独提取出来是为了解耦和
	//相当于线程入口函数只有一个包装，真正干活的是这个函数，这样最大的好处还是解耦和
	//一旦需要把服务器改成多进程或者IO多路复用的形式，整体代码的改动都是比较小的
	HandlerRequest(new_sock);
	return NULL;
}




//服务器启动
void HttpServerStart(const  char*  ip,short   port)
{
	int listen_sock=socket(AF_INET,SOCK_STREAM,0);
	if (listen_sock<0)
	{
		perror("socket");
		return ;
	}
    //加上这个选项是为了重用TIME_WAIT连接//解决出现大量TIME_WAIT的现象
    //服务器出现大量的TIME_WAIT状态，说明是服务器率先断开了连接，为了大量TIME_WAIT连接之后还能够
    //有新的连接可用，那么就需要重用已有的TIME_WAIT，加上SO_REUSEADDR进行重用
    //服务器出现大量的CLOSE_WAIT状态意味着代码出现了bug，四次挥手只出现了两次，没有正确的分配socket
    //导致服务器出错，文件泄漏，需要找到对应的代码进行修复。
	int opt=1;
	setsockopt(listen_sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof (opt));
    //加上这个函数是为了使端口处于TIME-WAIT时复用地址.
    //第一个参数表示要对哪一个参数进行设置，第二、三个参数决定了要给哪个选项设置一个值
    //最后两个参数决定了要设置什么样的值，SO_REUSEADDR重用addr连接。opt为1表示开启选项
	sockaddr_in addr;
	addr.sin_family=AF_INET;
	addr.sin_addr.s_addr=inet_addr(ip);
	addr.sin_port=htons(port);

	int ret=bind(listen_sock,(sockaddr*)&addr,sizeof (addr));
	if (ret<0)
	{
		perror("bind");
		return ;
	}
	ret=listen(listen_sock,5);
	if (ret<0)
	{
		perror("listen");
		return ;
	}
	printf ("ServerInit  OK\n");
	while (1)
	{
		sockaddr_in peer;
		socklen_t  len=sizeof (peer);
		int   new_sock=accept(listen_sock,(sockaddr*)&peer,&len);
		if (new_sock<0)
		{
			perror("accept");
			continue;
		}
		//使用多线程的方式来实现TCP服务器
		pthread_t  tid;
		//       printf ("ThreadEntry\n");
        //把new_sock传递给线程入口函数
		pthread_create(&tid,NULL,ThreadEntry,(void *)new_sock);
        //回收线程
		pthread_detach(tid);
	}

}




//主函数的参数   ./http_server  [ip]   [port]
int main (int argc,char*   argv[])
{
	if (argc!=3)
	{
		printf ("Usage   ./http_server  [ip]   [port]\n");
		return 1;
	}
	HttpServerStart( argv[1],atoi(argv[2]));
	return 0;
}
